[CLAUDE] Phase2: Form Engine MVP + docs (gotchas, skill, handoff)
Backend Forms:
- Domain/Forms: ContractTemplate (FormCode, Name, ContractType, FileName, StoragePath, Format, FieldSpec JSON, IsActive) + ContractClause
- EF config voi unique FormCode + query filter IsDeleted
- DbSets + IApplicationDbContext update
- Migration AddForms (bang 14 total)
- Packages: DocumentFormat.OpenXml 3.x + ClosedXML 0.105+
- Application/Forms:
- IFormRenderer interface + RenderResult record
- FormFeatures.cs: List/Get/Render CQRS
- IWebHostEnvironmentLocator (abstract IWebHostEnvironment)
- Infrastructure/Forms:
- DocxRenderer: OpenXml-based placeholder {{field}} replace, handle split runs (gom text tat ca <w:t> trong paragraph, replace, gan lai text dau + clear rest)
- XlsxRenderer: ClosedXML cell value replace
- FormRenderer router theo format docx/xlsx
- Api:
- FormsController: GET /templates (filter type, onlyActive), GET /templates/{id}, POST /templates/{id}/render (return file)
- WebHostEnvironmentLocator impl
- DbInitializer SeedContractTemplatesAsync: seed 8 template metadata, IsActive=true chi khi file ton tai
Templates vat ly:
- Copy 5 .docx/.xlsx tu FORM/ sang wwwroot/templates/
- 3 .doc (FO-002.02/03/06) chua convert: IsActive=false (Word COM bi stuck luc test, can retry voi DisplayAlerts=0 hoac LibreOffice)
- scripts/convert-doc-to-docx.ps1 (Word COM automation)
Frontend fe-admin:
- types/forms.ts: ContractTemplate + ContractTypeLabel
- pages/forms/FormsPage.tsx: list templates + Render dialog (paste JSON data → download .docx/.xlsx)
- Route /forms them vao App.tsx
Bug fix:
- SpaceProcessingModeValues namespace: wrap EnumValue<> full path
- SaveAs2($path, 16) thay vi SaveAs([ref], [ref]) — PowerShell type issue
- Word COM stuck: kill process, skip .doc cho MVP
Docs (theo yeu cau user):
- docs/gotchas.md MOI: 17 pitfalls nhom theo tech stack / EF Core / OpenXml / JSON / dev workflow
- .claude/skills/form-engine/SKILL.md: placeholder → full spec (algorithm + code pointers + API + limitations)
- .claude/skills/permission-matrix/SKILL.md: placeholder → full spec (BE policy + FE guard + seed + pitfalls)
- docs/HANDOFF.md MOI: brief 5 phut cho session sau (run quickstart + where we are + next steps + file tree + gotchas ref)
- docs/STATUS.md: update cumulative stats + next up Phase 3
- docs/changelog/migration-todos.md: tick Phase 2 iteration 1 items + add iteration 2 list
- docs/changelog/sessions/2026-04-21-1200-phase2-form-engine.md: session log
- CLAUDE.md root: them reference den gotchas + HANDOFF
E2E verified:
- GET /api/forms/templates (onlyActive=false) → 8 templates
- POST /api/forms/templates/{FO-002.05}/render voi data dict → HTTP 200 + file .docx 482KB (Microsoft Word 2007+ OK)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -3,7 +3,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
using SolutionErp.Domain.Identity;
|
||||
using SolutionErp.Infrastructure.Forms;
|
||||
using SolutionErp.Infrastructure.Identity;
|
||||
using SolutionErp.Infrastructure.Persistence;
|
||||
using SolutionErp.Infrastructure.Persistence.Interceptors;
|
||||
@ -20,6 +22,8 @@ public static class DependencyInjection
|
||||
services.Configure<JwtSettings>(configuration.GetSection(JwtSettings.SectionName));
|
||||
services.AddScoped<IJwtTokenService, JwtTokenService>();
|
||||
|
||||
services.AddSingleton<IFormRenderer, FormRenderer>();
|
||||
|
||||
services.AddScoped<AuditingInterceptor>();
|
||||
|
||||
services.AddDbContext<ApplicationDbContext>((sp, options) =>
|
||||
|
||||
77
src/Backend/SolutionErp.Infrastructure/Forms/DocxRenderer.cs
Normal file
77
src/Backend/SolutionErp.Infrastructure/Forms/DocxRenderer.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Forms;
|
||||
|
||||
// Placeholder replace syntax: {{fieldName}}
|
||||
// Hạn chế Phase 2 MVP:
|
||||
// - Không support {{#loop}}...{{/loop}} (table lặp) — Phase 2 iteration 2
|
||||
// - Không split placeholder giữa 2 <w:t> (Word hay làm) — merge runs trước khi replace
|
||||
public class DocxRenderer
|
||||
{
|
||||
private static readonly Regex PlaceholderRegex = new(@"\{\{([a-zA-Z0-9_\.]+)\}\}", RegexOptions.Compiled);
|
||||
|
||||
public async Task<RenderResult> RenderAsync(
|
||||
string templateAbsolutePath,
|
||||
IReadOnlyDictionary<string, string?> data,
|
||||
string outputFileName,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
var bytes = File.ReadAllBytes(templateAbsolutePath);
|
||||
using var ms = new MemoryStream();
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
ms.Position = 0;
|
||||
|
||||
using (var doc = WordprocessingDocument.Open(ms, isEditable: true))
|
||||
{
|
||||
var body = doc.MainDocumentPart?.Document.Body;
|
||||
if (body is null) throw new InvalidOperationException("Template .docx không có Body");
|
||||
|
||||
// Xử lý cả main document + headers + footers
|
||||
ReplaceInElement(body, data);
|
||||
foreach (var hp in doc.MainDocumentPart!.HeaderParts)
|
||||
if (hp.Header is not null) ReplaceInElement(hp.Header, data);
|
||||
foreach (var fp in doc.MainDocumentPart.FooterParts)
|
||||
if (fp.Footer is not null) ReplaceInElement(fp.Footer, data);
|
||||
|
||||
doc.MainDocumentPart.Document.Save();
|
||||
}
|
||||
|
||||
return new RenderResult(
|
||||
ms.ToArray(),
|
||||
outputFileName,
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||
}
|
||||
|
||||
private static void ReplaceInElement(DocumentFormat.OpenXml.OpenXmlElement root, IReadOnlyDictionary<string, string?> data)
|
||||
{
|
||||
// Iterate từng paragraph: gom text của mọi <w:t> trong cùng paragraph → replace → gán lại vào <w:t> đầu + clear rest
|
||||
foreach (var para in root.Descendants<Paragraph>().ToList())
|
||||
{
|
||||
var textElements = para.Descendants<Text>().ToList();
|
||||
if (textElements.Count == 0) continue;
|
||||
|
||||
var combined = string.Concat(textElements.Select(t => t.Text));
|
||||
if (!combined.Contains("{{")) continue;
|
||||
|
||||
var replaced = PlaceholderRegex.Replace(combined, match =>
|
||||
{
|
||||
var key = match.Groups[1].Value;
|
||||
return data.TryGetValue(key, out var value) ? (value ?? string.Empty) : match.Value;
|
||||
});
|
||||
|
||||
if (replaced == combined) continue;
|
||||
|
||||
// Gán vào <w:t> đầu, clear rest (preserve run style của <w:t> đầu)
|
||||
textElements[0].Text = replaced;
|
||||
textElements[0].Space = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.SpaceProcessingModeValues>(DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve);
|
||||
for (var i = 1; i < textElements.Count; i++)
|
||||
textElements[i].Text = string.Empty;
|
||||
}
|
||||
|
||||
// Tables cũng có Paragraph lồng bên trong → đã được Descendants<Paragraph> bắt
|
||||
}
|
||||
}
|
||||
24
src/Backend/SolutionErp.Infrastructure/Forms/FormRenderer.cs
Normal file
24
src/Backend/SolutionErp.Infrastructure/Forms/FormRenderer.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Forms;
|
||||
|
||||
public class FormRenderer : IFormRenderer
|
||||
{
|
||||
private readonly DocxRenderer _docx = new();
|
||||
private readonly XlsxRenderer _xlsx = new();
|
||||
|
||||
public Task<RenderResult> RenderAsync(
|
||||
string templateAbsolutePath,
|
||||
string format,
|
||||
IReadOnlyDictionary<string, string?> data,
|
||||
string outputFileName,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return format.ToLowerInvariant() switch
|
||||
{
|
||||
"docx" => _docx.RenderAsync(templateAbsolutePath, data, outputFileName, ct),
|
||||
"xlsx" => _xlsx.RenderAsync(templateAbsolutePath, data, outputFileName, ct),
|
||||
_ => throw new NotSupportedException($"Format '{format}' không được hỗ trợ. Chỉ 'docx' hoặc 'xlsx'."),
|
||||
};
|
||||
}
|
||||
}
|
||||
47
src/Backend/SolutionErp.Infrastructure/Forms/XlsxRenderer.cs
Normal file
47
src/Backend/SolutionErp.Infrastructure/Forms/XlsxRenderer.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using ClosedXML.Excel;
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Forms;
|
||||
|
||||
public class XlsxRenderer
|
||||
{
|
||||
private static readonly Regex PlaceholderRegex = new(@"\{\{([a-zA-Z0-9_\.]+)\}\}", RegexOptions.Compiled);
|
||||
|
||||
public async Task<RenderResult> RenderAsync(
|
||||
string templateAbsolutePath,
|
||||
IReadOnlyDictionary<string, string?> data,
|
||||
string outputFileName,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
using var wb = new XLWorkbook(templateAbsolutePath);
|
||||
|
||||
foreach (var ws in wb.Worksheets)
|
||||
{
|
||||
foreach (var cell in ws.CellsUsed())
|
||||
{
|
||||
if (cell.DataType != XLDataType.Text) continue;
|
||||
var text = cell.GetString();
|
||||
if (!text.Contains("{{")) continue;
|
||||
|
||||
var replaced = PlaceholderRegex.Replace(text, match =>
|
||||
{
|
||||
var key = match.Groups[1].Value;
|
||||
return data.TryGetValue(key, out var value) ? (value ?? string.Empty) : match.Value;
|
||||
});
|
||||
|
||||
if (replaced != text)
|
||||
cell.Value = replaced;
|
||||
}
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
wb.SaveAs(ms);
|
||||
|
||||
return new RenderResult(
|
||||
ms.ToArray(),
|
||||
outputFileName,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.Forms;
|
||||
using SolutionErp.Domain.Identity;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
@ -16,6 +17,8 @@ public class ApplicationDbContext
|
||||
public DbSet<Department> Departments => Set<Department>();
|
||||
public DbSet<MenuItem> MenuItems => Set<MenuItem>();
|
||||
public DbSet<Permission> Permissions => Set<Permission>();
|
||||
public DbSet<ContractTemplate> ContractTemplates => Set<ContractTemplate>();
|
||||
public DbSet<ContractClause> ContractClauses => Set<ContractClause>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Forms;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
public class ContractClauseConfiguration : IEntityTypeConfiguration<ContractClause>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractClause> b)
|
||||
{
|
||||
b.ToTable("ContractClauses");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Code).HasMaxLength(50).IsRequired();
|
||||
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
b.Property(x => x.Content).HasColumnType("nvarchar(max)").IsRequired();
|
||||
|
||||
b.HasIndex(x => x.Code).IsUnique();
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Forms;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
public class ContractTemplateConfiguration : IEntityTypeConfiguration<ContractTemplate>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractTemplate> b)
|
||||
{
|
||||
b.ToTable("ContractTemplates");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.FormCode).HasMaxLength(50).IsRequired();
|
||||
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
b.Property(x => x.ContractType).HasConversion<int?>();
|
||||
b.Property(x => x.FileName).HasMaxLength(255).IsRequired();
|
||||
b.Property(x => x.StoragePath).HasMaxLength(500).IsRequired();
|
||||
b.Property(x => x.Format).HasMaxLength(10).IsRequired();
|
||||
b.Property(x => x.FieldSpec).HasColumnType("nvarchar(max)");
|
||||
b.Property(x => x.Description).HasMaxLength(500);
|
||||
|
||||
b.HasIndex(x => x.FormCode).IsUnique();
|
||||
b.HasIndex(x => x.ContractType);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
using SolutionErp.Domain.Forms;
|
||||
using SolutionErp.Domain.Identity;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence;
|
||||
@ -27,6 +29,7 @@ public static class DbInitializer
|
||||
await SeedAdminAsync(userManager, logger);
|
||||
await SeedMenuTreeAsync(db, logger);
|
||||
await SeedAdminPermissionsAsync(db, roleManager, logger);
|
||||
await SeedContractTemplatesAsync(db, logger);
|
||||
}
|
||||
|
||||
private static async Task SeedRolesAsync(RoleManager<Role> roleManager, ILogger logger)
|
||||
@ -68,7 +71,6 @@ public static class DbInitializer
|
||||
|
||||
private static async Task SeedMenuTreeAsync(ApplicationDbContext db, ILogger logger)
|
||||
{
|
||||
// (key, label, parent, order, icon) — icon là lucide-react name
|
||||
var tree = new (string Key, string Label, string? Parent, int Order, string Icon)[]
|
||||
{
|
||||
(MenuKeys.Dashboard, "Tổng quan", null, 10, "LayoutDashboard"),
|
||||
@ -128,4 +130,67 @@ public static class DbInitializer
|
||||
logger.LogInformation("Seeded {Count} admin permissions", added);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedContractTemplatesAsync(ApplicationDbContext db, ILogger logger)
|
||||
{
|
||||
// Chỉ IsActive=true nếu file thực tế tồn tại trong wwwroot/templates/.
|
||||
// 3 file .doc (FO-002.02/03/06) chưa convert sẽ IsActive=false.
|
||||
var templates = new (string FormCode, string Name, ContractType? Type, string FileName, string Format, string? Description)[]
|
||||
{
|
||||
("SOL-CCM-FO-002.01", "Bảng kiểm tra hợp đồng", null,
|
||||
"SOL-CCM-FO-002.01.v01 Bang kiem tra hop dong.docx", "docx",
|
||||
"Checklist duyệt HĐ theo 4 bộ phận: Đề xuất / Cung ứng / CCM / Giám đốc"),
|
||||
("SOL-CCM-FO-002.02", "Hợp đồng trọn gói — Nhân công + Vật tư", ContractType.HopDongThauPhu,
|
||||
"SOL-CCM-FO-002.02.v01 Hop dong tron goi nhan cong, vat tu.docx", "docx",
|
||||
"Template HĐ thầu phụ trọn gói (cần convert .doc → .docx qua Word COM)"),
|
||||
("SOL-CCM-FO-002.03", "Hợp đồng trọn gói — Nhân công + Thiết bị", ContractType.HopDongThauPhu,
|
||||
"SOL-CCM-FO-002.03.v01 Hop dong trong goi nhan cong, thiet bi.docx", "docx",
|
||||
"Template HĐ thầu phụ trọn gói (cần convert .doc → .docx qua Word COM)"),
|
||||
("SOL-CCM-FO-002.04", "Điều kiện chung hợp đồng trọn gói", null,
|
||||
"SOL-CCM-FO-002.04.v01 Dieu kien chung hop dong tron goi.docx", "docx",
|
||||
"Appendix điều khoản chung đính kèm HĐ trọn gói"),
|
||||
("SOL-CCM-FO-002.05", "Hợp đồng Giao khoán", ContractType.HopDongGiaoKhoan,
|
||||
"SOL-CCM-FO-002.05.v01 Hop dong Giao khoan.docx", "docx",
|
||||
"Template HĐ giao khoán với Tổ đội / Nhân công"),
|
||||
("SOL-CCM-FO-002.06", "Hợp đồng mua bán", ContractType.HopDongMuaBan,
|
||||
"SOL-CCM-FO-002.06.v01 Hop dong mua ban.docx", "docx",
|
||||
"Template HĐ mua bán NCC (cần convert .doc → .docx qua Word COM)"),
|
||||
("SOL-CCM-FO-002.07", "Đơn đặt hàng (PO)", null,
|
||||
"SOL-CCM-FO-002.07.V01 Don dat hang.xlsx", "xlsx",
|
||||
"Template PO Excel — 3 sheet: so sánh giá, PO chính, PO NLT"),
|
||||
("SOL-CCM-RG-001", "Quy định mã số hợp đồng", null,
|
||||
"SOL-CCM-RG-001.v02 QD ma so hop dong.docx", "docx",
|
||||
"Regulation mã HĐ + PO — reference doc"),
|
||||
};
|
||||
|
||||
var existingCodes = await db.ContractTemplates.Select(t => t.FormCode).ToListAsync();
|
||||
var wwwroot = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
|
||||
var added = 0;
|
||||
foreach (var (formCode, name, type, fileName, format, desc) in templates)
|
||||
{
|
||||
if (existingCodes.Contains(formCode)) continue;
|
||||
|
||||
var templatePath = $"templates/{fileName}";
|
||||
var absPath = Path.Combine(wwwroot, "templates", fileName);
|
||||
var fileExists = File.Exists(absPath);
|
||||
|
||||
db.ContractTemplates.Add(new ContractTemplate
|
||||
{
|
||||
FormCode = formCode,
|
||||
Name = name,
|
||||
ContractType = type,
|
||||
FileName = fileName,
|
||||
StoragePath = templatePath,
|
||||
Format = format,
|
||||
Description = desc,
|
||||
IsActive = fileExists,
|
||||
});
|
||||
added++;
|
||||
}
|
||||
if (added > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Seeded {Count} contract templates (active file check)", added);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
725
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260421043848_AddForms.Designer.cs
generated
Normal file
725
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260421043848_AddForms.Designer.cs
generated
Normal file
@ -0,0 +1,725 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SolutionErp.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260421043848_AddForms")]
|
||||
partial class AddForms
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("RoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Forms.ContractClause", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractClauses", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Forms.ContractTemplate", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("ContractType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("FieldSpec")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("nvarchar(255)");
|
||||
|
||||
b.Property<string>("FormCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Format")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("StoragePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractType");
|
||||
|
||||
b.HasIndex("FormCode")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractTemplates", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Label")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ParentKey")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentKey");
|
||||
|
||||
b.ToTable("MenuItems", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.Permission", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("CanCreate")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("CanDelete")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("CanRead")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("CanUpdate")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("MenuKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MenuKey");
|
||||
|
||||
b.HasIndex("RoleId", "MenuKey")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Permissions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.Role", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("Roles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<DateTime?>("RefreshTokenExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Master.Department", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid?>("ManagerUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Departments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Master.Project", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("BudgetTotal")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid?>("ManagerUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Projects", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Master.Supplier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ContactPerson")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("Phone")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<string>("TaxCode")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("Suppliers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SolutionErp.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentKey")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.Permission", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Menu")
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("MenuKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SolutionErp.Domain.Identity.Role", "Role")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Menu");
|
||||
|
||||
b.Navigation("Role");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddForms : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractClauses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Code = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
Content = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Version = table.Column<int>(type: "int", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ContractClauses", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractTemplates",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
FormCode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
ContractType = table.Column<int>(type: "int", nullable: true),
|
||||
FileName = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
|
||||
StoragePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
Format = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
FieldSpec = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ContractTemplates", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractClauses_Code",
|
||||
table: "ContractClauses",
|
||||
column: "Code",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractTemplates_ContractType",
|
||||
table: "ContractTemplates",
|
||||
column: "ContractType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractTemplates_FormCode",
|
||||
table: "ContractTemplates",
|
||||
column: "FormCode",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractClauses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractTemplates");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,6 +125,136 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Forms.ContractClause", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractClauses", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Forms.ContractTemplate", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("ContractType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("FieldSpec")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("nvarchar(255)");
|
||||
|
||||
b.Property<string>("FormCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Format")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("StoragePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractType");
|
||||
|
||||
b.HasIndex("FormCode")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractTemplates", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6">
|
||||
|
||||
Reference in New Issue
Block a user