diff --git a/public/app.js b/public/app.js index 1151163..b269baa 100644 --- a/public/app.js +++ b/public/app.js @@ -23,7 +23,10 @@ document.getElementById('leaseStart').addEventListener('change', (e) => { schedulePreview(); }); -templateSelect.addEventListener('change', () => loadPreview()); +templateSelect.addEventListener('change', () => { + loadTemplateDefaults(templateSelect.value); + loadPreview(); +}); let previewTimer = null; function schedulePreview() { @@ -50,6 +53,7 @@ form.addEventListener('submit', async (event) => { monthlyRent: formData.get('monthlyRent'), paymentDay: formData.get('paymentDay'), deposit: formData.get('deposit'), + cleaningFee: formData.get('cleaningFee'), leaseStart: formData.get('leaseStart'), leaseEnd: formData.get('leaseEnd'), }), @@ -119,6 +123,7 @@ async function loadTemplates() { } connectionStatus.textContent = '可使用'; + loadTemplateDefaults(templateSelect.value); loadPreview(); } catch (error) { templateSelect.append(new Option('讀取失敗', '')); @@ -129,6 +134,19 @@ async function loadTemplates() { } } +async function loadTemplateDefaults(name) { + if (!name) return; + try { + const res = await fetch(`/api/templates/${encodeURIComponent(name)}/defaults`); + if (!res.ok) return; + const defaults = await res.json(); + if (defaults.monthlyRent) form.elements.monthlyRent.value = defaults.monthlyRent; + if (defaults.paymentDay) form.elements.paymentDay.value = defaults.paymentDay; + if (defaults.deposit) form.elements.deposit.value = defaults.deposit; + if (defaults.cleaningFee) form.elements.cleaningFee.value = defaults.cleaningFee; + } catch {} +} + let previewGen = 0; let previewAbortController = null; let previewBlobUrl = null; @@ -151,6 +169,7 @@ async function loadPreview() { monthlyRent: formData.get('monthlyRent') || '', paymentDay: formData.get('paymentDay') || '', deposit: formData.get('deposit') || '', + cleaningFee: formData.get('cleaningFee') || '', leaseStart: formData.get('leaseStart') || '', leaseEnd: formData.get('leaseEnd') || '', }); diff --git a/public/index.html b/public/index.html index 7a4829b..0c6c3c2 100644 --- a/public/index.html +++ b/public/index.html @@ -50,7 +50,7 @@
-
+
+
diff --git a/server.js b/server.js index cee47fb..35fc7c0 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,8 @@ const { getTemplatePreviewPdf, saveTemplate, deleteTemplate, + getTemplateDefaults, + saveTemplateDefaults, } = require('./src/contractService'); const app = express(); @@ -61,6 +63,14 @@ app.get('/api/templates', async (_req, res, next) => { } }); +app.get('/api/templates/:name/defaults', async (req, res, next) => { + try { + res.json(await getTemplateDefaults(req.params.name)); + } catch (error) { + next(error); + } +}); + app.get('/api/templates/:name/preview', async (req, res) => { try { const pdfBuffer = await getTemplatePreviewPdf(req.params.name, req.query); @@ -82,10 +92,18 @@ app.post('/api/contracts/pdf', async (req, res) => { monthlyRent: req.body.monthlyRent, paymentDay: req.body.paymentDay, deposit: req.body.deposit, + cleaningFee: req.body.cleaningFee, leaseStart: req.body.leaseStart, leaseEnd: req.body.leaseEnd, }); + await saveTemplateDefaults(req.body.template, { + monthlyRent: req.body.monthlyRent, + paymentDay: req.body.paymentDay, + deposit: req.body.deposit, + cleaningFee: req.body.cleaningFee, + }).catch(() => {}); + res.setHeader('Content-Type', 'application/pdf'); res.setHeader( 'Content-Disposition', diff --git a/src/contractService.js b/src/contractService.js index 4c7a518..1d3f355 100644 --- a/src/contractService.js +++ b/src/contractService.js @@ -20,6 +20,7 @@ const PLACEHOLDERS = [ ['{{每月租金}}', 'monthlyRent', '每月租金', true], ['{{繳款日期}}', 'paymentDay', '繳款日期', true], ['{{保證金}}', 'deposit', '保證金', true], + ['{{清潔費}}', 'cleaningFee', '清潔費', true], ['{{租賃開始年}}', 'leaseStartYear', '租賃開始年', false], ['{{租賃開始月}}', 'leaseStartMonth', '租賃開始月', false], ['{{租賃開始日}}', 'leaseStartDay', '租賃開始日', false], @@ -84,6 +85,7 @@ function normalizeContractInput(input) { monthlyRent: normalizePositiveInteger(input.monthlyRent, '每月租金'), paymentDay: normalizePaymentDay(input.paymentDay), deposit: normalizePositiveInteger(input.deposit, '保證金'), + cleaningFee: normalizePositiveInteger(input.cleaningFee, '清潔費'), leaseStartYear: String(start.rocYear), leaseStartMonth: String(start.month), leaseStartDay: String(start.day), @@ -362,6 +364,7 @@ function buildPreviewValues(input) { if (input.monthlyRent) values.monthlyRent = String(input.monthlyRent); if (input.paymentDay) values.paymentDay = String(input.paymentDay); if (input.deposit) values.deposit = String(input.deposit); + if (input.cleaningFee) values.cleaningFee = String(input.cleaningFee); if (input.leaseStart && /^\d{4}-\d{2}-\d{2}$/.test(input.leaseStart)) { const [y, m, d] = input.leaseStart.split('-').map(Number); @@ -424,6 +427,30 @@ async function getTemplatePreviewPdf(templateName, input = {}) { } } +const DEFAULTS_FILE = path.resolve(TEMPLATE_DIR, 'defaults.json'); +const DEFAULT_CLEANING_FEE = '1200'; + +async function getTemplateDefaults(templateName) { + try { + const raw = await fs.readFile(DEFAULTS_FILE, 'utf-8'); + const all = JSON.parse(raw); + return { cleaningFee: DEFAULT_CLEANING_FEE, ...all[templateName] }; + } catch { + return { cleaningFee: DEFAULT_CLEANING_FEE }; + } +} + +async function saveTemplateDefaults(templateName, values) { + let all = {}; + try { + const raw = await fs.readFile(DEFAULTS_FILE, 'utf-8'); + all = JSON.parse(raw); + } catch {} + all[templateName] = { ...all[templateName], ...values }; + await fs.mkdir(TEMPLATE_DIR, { recursive: true }); + await fs.writeFile(DEFAULTS_FILE, JSON.stringify(all, null, 2), 'utf-8'); +} + async function saveTemplate(originalName, buffer) { const name = path.basename(String(originalName)); if (!name) throwClientError('檔名不正確。'); @@ -447,4 +474,6 @@ module.exports = { getTemplatePreviewPdf, saveTemplate, deleteTemplate, + getTemplateDefaults, + saveTemplateDefaults, }; diff --git a/templates/租屋契約-內容_逢甲 A.docx b/templates/租屋契約-內容_逢甲 A.docx index df20585..ad9fbd6 100644 Binary files a/templates/租屋契約-內容_逢甲 A.docx and b/templates/租屋契約-內容_逢甲 A.docx differ diff --git a/templates/租屋契約-內容_逢甲 B.docx b/templates/租屋契約-內容_逢甲 B.docx index e7cb3cc..c1985be 100644 Binary files a/templates/租屋契約-內容_逢甲 B.docx and b/templates/租屋契約-內容_逢甲 B.docx differ diff --git a/templates/租屋契約-內容_逢甲 C.docx b/templates/租屋契約-內容_逢甲 C.docx index 45b140a..177e3b1 100644 Binary files a/templates/租屋契約-內容_逢甲 C.docx and b/templates/租屋契約-內容_逢甲 C.docx differ diff --git a/templates/租屋契約-內容_逢甲 D.docx b/templates/租屋契約-內容_逢甲 D.docx index 4f578e9..25b7a58 100644 Binary files a/templates/租屋契約-內容_逢甲 D.docx and b/templates/租屋契約-內容_逢甲 D.docx differ